这是一篇Property和KVO爱恨纠葛的文章。
前言
由于系统KVO没有block回调,所以本人通过模拟系统常规KVO的实现方式去进行了block的添加,也就是常见的重写被监听对象的setter方法。正常情况下一切顺利,然而当自定义的KVO遇到readonly时,就发生了一些好玩的事情。
KVO
KVO概述
KVO的全称是keyValueObserving,也就是观察者模式在iOS的实现之一。KVO的作用就是用来监听某个属性值的变化或者集合中元素的变化。
KVO被触发的原因
表面原因:
- 对于普通属性变化,只需要通过属性的setter方法改变值就可以了;
- 对于readonly属性变化,则有两种办法:一是调用
willChangeValueForKey
和didChangeValueForKey
;二是使用KVC; - 对于数组元素的变化,需要在对元素增删替代时,使用
mutableArrayValueForKey
方法。字典
和集合
等都有其对应的方法。
根本原因:
通过不同的手段最终触发了NSKeyValueNotifyObserver
函数。这个函数是个内部函数,但你可以在observeValueForKeyPath:ofObject:change:context:
中打断点,然后再调用栈中看到它。
自定义KVO的实现原理
- 建立被观察对象所属类的子类;
- 将被观察对象所属的实例的类指向创建的子类;
- 重写被观察对象的setter方法,在其中触发你传进来的block;如果是系统的话,就是触发
willChangeValueForKey
和didChangeValueForKey
。
监听readonly的特质的属性?
众所周知,如果你监听的属性是只读的,那么KVO毫无作用,因为只读属性是不存在setter方法的。
那么,不知道你有没有关注过WKWebView
的title
、canGoBack
、estimatedProgress
等属性呢?这些属性都是只读属性,但它们可以触发KVO。下面是WKWebView
的title
的官方文档:
|
|
自定义KVO的问题
实现方式一:重写setter方法
如果你仔细看了上面的文字,其实就大概可以猜到WKWebView
的这些只读属性是如何触发KVO的了。
这个时候问题就来了,当你使用自己定义的KVO时,你并不能获取到数据的更改了。因为你是基于setter方法的重写,而只读属性是绕过了setter方法,直接使用了上述针对readonly的两种办法。
这个时候你基于你现有的自定义实现毫无办法去解决这个问题,那么就只能抛弃自定义实现喽。
实现方式二:重写observeValueForKeyPath:ofObject:change:context:方法
基于这种实现方式的轮子,其实facebook已经实现了,那就是KVOController
。有兴趣的话可以去github上看下,链接在此。
它的实现方式大致就是通过重写observeValueForKeyPath:ofObject:change:context:
方法来进行监听分发。由于触发KVO的根本原因是触发了NSKeyValueNotifyObserver
函数,这个函数会调用observeValueForKeyPath:ofObject:change:context:
,所以基于这种实现的自定义KVO可以接收到任何的KVO触发。
总结
感觉这个知识点好短啊,总结就两句话。
- 如果自定义KVO,就考虑使用Facebook那一套或者以
observeValueForKeyPath:ofObject:change:context:
方法为实现基础。 - 老老实实使用系统提供的KVO。